/*
	Copyright (C) 2010 Salil Bhagurkar

	This file is part of k3

	k3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	k3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with k3.  If not, see <http://www.gnu.org/licenses/>.
*/

/*(Table 00234)
Values for disk operation status:
 00h	successful completion
 01h	invalid function in AH or invalid parameter
 02h	address mark not found
 03h	disk write-protected
 04h	sector not found/read error
 05h	reset failed (hard disk)
 05h	data did not verify correctly (TI Professional PC)
 06h	disk changed (floppy)
 07h	drive parameter activity failed (hard disk)
 08h	DMA overrun
 09h	data boundary error (attempted DMA across 64K boundary or >80h sectors)
 0Ah	bad sector detected (hard disk)
 0Bh	bad track detected (hard disk)
 0Ch	unsupported track or invalid media
 0Dh	invalid number of sectors on format (PS/2 hard disk)
 0Eh	control data address mark detected (hard disk)
 0Fh	DMA arbitration level out of range (hard disk)
 10h	uncorrectable CRC or ECC error on read
 11h	data ECC corrected (hard disk)
 20h	controller failure
 31h	no media in drive (IBM/MS INT 13 extensions)
 32h	incorrect drive type stored in CMOS (Compaq)
 40h	seek failed
 80h	timeout (not ready)
 AAh	drive not ready (hard disk)
 B0h	volume not locked in drive (INT 13 extensions)
 B1h	volume locked in drive (INT 13 extensions)
 B2h	volume not removable (INT 13 extensions)
 B3h	volume in use (INT 13 extensions)
 B4h	lock count exceeded (INT 13 extensions)
 B5h	valid eject request failed (INT 13 extensions)
 B6h	volume present but read protected (INT 13 extensions)
 BBh	undefined error (hard disk)
 CCh	write fault (hard disk)
 E0h	status register error (hard disk)
 FFh	sense operation failed (hard disk)
*/

#include <shared.h>


static int read_bytewise(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count,
			int (*read_function)(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count));
static int write_bytewise(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count,
		int (*read_function)(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count),
		int (*write_function)(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count));

#define BD_INT 0x13
#define SECT_SZ 512

#define high(register) ((u8)(register>>8))

static int reset_disk(u8 drive)
{
	struct regs regs={0};
	regs.edx=drive;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high(((u16)regs.eax));
	}
	return 0;
}

static int reset_disk_retry(u8 drive,int retries)
{
	while(--retries>0)	{
		if((reset_disk(drive))==0)
			return 0;
	}
	return ELLERR;
}

#define DRIVE 0x80

struct drive_struct	{
	int cylinders;
	int sectors;
	int heads;
	u32 total_sect;
};

static struct drive_struct drive, myparameters;

static int get_drive_parameters()
{
	int cl;
	struct regs regs={0};
	reset_disk_retry(0x80,3);
	regs.eax=0x800;
	regs.edx=DRIVE;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return ELLERR;
	}
	cl=(u8)regs.ecx;
	drive.cylinders=high(((u16)regs.ecx));
	drive.cylinders|=((cl & 0xc0)<<2);
	drive.sectors=cl & 0x3f;
	drive.heads=high(((u16)regs.edx))+1;
	return 0;
}

static int get_chs(u32 LBA)
{
	if(drive.sectors==0)	{
		return ESTATEINV;
	}
	myparameters.sectors=(LBA%(drive.sectors))+1;
	myparameters.cylinders=(LBA/(drive.sectors))/(drive.heads);
	myparameters.heads=(LBA/(drive.sectors))%(drive.heads);
	return 0;
}

static int _read_prim(struct fs *referrer, struct fs *this, void *buf,u32 LBA,u32 n)
{
	u8 ch,cl;
	u16 c;
	u16 nr;
	struct regs regs={0};
	get_chs(LBA);
	ch=(u8)(myparameters.cylinders);
	cl=myparameters.sectors;
	cl|=(((myparameters.cylinders)&768)>>2);
	c=ch*0x100+cl;
	nr=(u16)n;
	regs.eax=0x0200+nr;
	regs.ebx=0x1000;
	regs.ecx=c;
	regs.edx=(myparameters.heads)*0x100+0x80;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high((u16)regs.eax);
	}
	memcpy(buf,(void *)0x1000,n*0x200);
	return 0;
}

static int _write_prim(struct fs *referrer, struct fs *this, void *buf, u32 LBA, u32 n)
{
	u8 ch,cl;
	u16 c;
	u16 nr;
	struct regs regs={0};
	get_chs(LBA);
	ch=(u8)(myparameters.cylinders);
	cl=myparameters.sectors;
	cl|=(((myparameters.cylinders)&768)>>2);
	c=ch*0x100+cl;
	nr=(u16)n;
	regs.eax=0x0300+nr;
	regs.ebx=0x1000;
	regs.ecx=c;
	regs.edx=(myparameters.heads)*0x100+0x80;
	regs.f=BD_INT;
	memcpy((void *)0x1000,buf,n*0x200);
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high((u16)regs.eax);
	}
	return 0;
}


static int read_prim(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	return read_bytewise(referrer, this, buf->data, offset, count, _read_prim);
}

static int write_prim(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	return write_bytewise(referrer, this, buf->data, offset, count, _read_prim, _write_prim);
}


struct dap_struct	{
	u8 size;
	u8 rsvd;
	u16 n;
	u32 buf;
	u64 start;
}*dap=(struct dap_struct *)0x9000;

struct drive_parms {
	u16 sz;
	u16 info;
	u32 cyl;
	u32 head;
	u32 sect;
	u64 total_sect;
	u8 pad[200];
}__attribute__((packed));
static struct drive_parms *drive_parms=(struct drive_parms *)0x9100;

static int check_ext()
{
	struct regs regs={0};
	regs.eax=0x4100;
	regs.ebx=0x55aa;
	regs.ecx=0;
	regs.edx=0x80;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.ebx==0xaa55)	{
	} else	{
		return ENOSUP;
	}
	regs.eax=0x4800;
	regs.ebx=0;
	regs.edx=0x80;
	regs.esi=(u32)drive_parms;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1) {
		return ELLERR;
	}
	return 0;
}

static int _read_drive(struct fs *referrer, struct fs *this, void *buf,u32 LBA,u32 n)
{
	struct regs regs={0};
	if(!buf)
		return EPINV;
	if(LBA > drive_parms->total_sect)
		return EPINV;
	dap->n=(u16)n;
	dap->buf=0x1000;
	dap->start=LBA;
	regs.eax=0x4200;
	regs.ebx=0;
	regs.ecx=0;
	regs.edx=0x80;
	regs.esi=(u32)dap;
	regs.edi=0;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	memcpy(buf,(u8 *)0x1000,(dap->n)* SECT_SZ);
	if(regs.f & 1) {
		return (u16)regs.eax;
	} else {
		return 0;
	}
}

static int __read_drive(struct fs *referrer, struct fs *this, void *buf, u32 LBA, u32 n)
{
	int err = 0;
	u32 i = 0;
	while(n--) {
		err = _read_drive(referrer, this, buf + SECT_SZ * i, LBA + i, 1);
		i++;
	}
	return err;
}

static int _write_drive(struct fs *referrer, struct fs *this, void *buf,u32 LBA,u32 n)
{
	struct regs regs={0};
	if(!buf)
		return EPINV;
	if(LBA > drive_parms->total_sect)
		return EPINV;
	dap->n=(u16)n;
	dap->buf=0x1000;
	dap->start=LBA;
	regs.eax=0x4300;
	regs.ebx=0;
	regs.ecx=0;
	regs.edx=0x80;
	regs.esi=(u32)dap;
	regs.edi=0;
	regs.f=BD_INT;
	memcpy((u8 *)0x1000, buf, (dap->n)* SECT_SZ);
	arch_bios_int(&regs);
	if(regs.f & 1) {
		return (u16)regs.eax;
	} else {
		return 0;
	}
}

static int __write_drive(struct fs *referrer, struct fs *this, void *buf, u32 LBA, u32 n)
{
	int err = 0;
	u32 i = 0;
	while(n--) {
		err = _write_drive(referrer, this, buf + SECT_SZ * i, LBA + i, 1);
		i++;
	}
	return err;
}

static int read_drive(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	return read_bytewise(referrer, this, buf->data, offset, count, __read_drive);
}

static int write_drive(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	return write_bytewise(referrer, this, buf->data, offset, count, __read_drive, __write_drive);
}

static struct fs *init_biosdisk()
{
	struct fs *hd0_fs = fs_create(FSFLAGS_DEVICE, "hd0");
	u8 buf[SECT_SZ];
	reset_disk(DRIVE);
	if(!check_ext()) {
		dprint(D_INFO, "hd0", "%u blocks",drive_parms->total_sect);
		drive.total_sect = hd0_fs->buf.len = drive_parms->total_sect;
		hd0_fs->system = fs_create_system(0, read_drive, write_drive, dummy, hd0_fs);
	} else {
		dprint(D_INFO, "hd0", "Using legacy read");
		get_drive_parameters();
		dprint(D_INFO, "hd0", "chs : %d,%d,%d\n",drive.cylinders,drive.heads,drive.sectors);
		drive.total_sect = hd0_fs->buf.len = drive.cylinders * drive.heads * drive.sectors;
		hd0_fs->system = fs_create_system(0, read_prim, write_prim, dummy, hd0_fs);
	}
	return hd0_fs;
}



struct drive_struct fdparams;

static int get_chs_fd(u32 LBA)
{
	fdparams.sectors=(LBA%(18))+1;
	fdparams.cylinders=(LBA/(18))/(2);
	fdparams.heads=(LBA/(18))%(2);
	return 0;
}

static int _read_fd(struct fs *referrer, struct fs *this, void *buf,u32 LBA,u32 n)
{
	u8 ch,cl;
	u16 c;
	u16 nr;
	struct regs regs={0};
	get_chs_fd(LBA);
	ch=(u8)(fdparams.cylinders);
	cl=fdparams.sectors;
	cl|=(((fdparams.cylinders)&768)>>2);
	c=ch*0x100+cl;
	nr=(u16)n;
	regs.eax=0x0200+nr;
	regs.ebx=0x1000;
	regs.ecx=c;
	regs.edx=(fdparams.heads)*0x100;
	regs.f=BD_INT;
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high((u16)regs.eax);
	}
	memcpy(buf,(void *)0x1000,n*0x200);
	return 0;
}

static int _write_fd(struct fs *referrer, struct fs *this, void *buf,u32 LBA,u32 n)
{
	u8 ch,cl;
	u16 c;
	u16 nr;
	struct regs regs={0};
	get_chs_fd(LBA);
	ch=(u8)(fdparams.cylinders);
	cl=fdparams.sectors;
	cl|=(((fdparams.cylinders)&768)>>2);
	c=ch*0x100+cl;
	nr=(u16)n;
	regs.eax=0x0300+nr;
	regs.ebx=0x1000;
	regs.ecx=c;
	regs.edx=(fdparams.heads)*0x100;
	regs.f=BD_INT;
	memcpy((void *)0x1000, buf, n*0x200);
	arch_bios_int(&regs);
	if(regs.f & 1)	{
		return high((u16)regs.eax);
	}
	return 0;
}

static int read_fd(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	return read_bytewise(referrer, this, buf->data, offset, count, _read_fd);
}

static int write_fd(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	return write_bytewise(referrer, this, buf->data, offset, count, _read_fd, _write_fd);
}

static struct fs *init_fd()
{
	struct fs *fd0_fs;
	fd0_fs = fs_create(FSFLAGS_DEVICE, "fd0");
	u8 buf[SECT_SZ];
	if(reset_disk_retry(0, 3))
		return NULL;
	fd0_fs->buf.len = 2880; //:( Not much support
	fd0_fs->system = fs_create_system(0, read_fd, write_fd, dummy, fd0_fs);
	return fd0_fs;
}

static int biosdisk_write(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	struct fs *fs;
	switch(offset) {
		case DRIVER_START:
			fs = init_biosdisk();
			if(fs)
				fs_link(fsparse_get("/devices/class/store/block"), fs);
			fs = init_fd();
			if(fs)
				fs_link(fsparse_get("/devices/class/store/block"), fs);
			return 0;
		case DRIVER_STOP:
			fs_unlink(fsparse_get("/devices/class/store/block/hd0"));
			fs_unlink(fsparse_get("/devices/class/store/block/fd0"));
			return 0;
		default:
			return ENOSUP;
	}
	return 0;
}

int register_biosdisk()
{
	struct fs *biosdisk_driver = fs_create(FSFLAGS_DEVICE_DRIVER, "biosdisk");
	biosdisk_driver->system = fs_create_system(0, dummy, biosdisk_write, dummy, biosdisk_driver);
	fs_link(fsparse_get("/drivers"), biosdisk_driver);
	return 0;
}




/**
 * Bytewise readers
 */
static int read_bytewise(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count,
			int (*read_function)(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count))
{
	int err;
	u32 block_offset;
	u32 block_end; //Ending block offset
	u32 block_count;
	u32 offset_in_block;
	//Calculate the offset in form of blocks
	block_offset = offset / SECT_SZ;
	//Calculatge the last block address
	block_end = (offset + count) / SECT_SZ;
	//Calculate the number of blocks to be read
	block_count =  block_end - block_offset + 1;
	//Calculate the offset starting which we should copy the data from source block buffer
	offset_in_block = offset - block_offset * SECT_SZ;
	//Allocate a temporary block buffer to store the entire blocks
	void *temp;
	temp = malloc_get(block_count * SECT_SZ, "bdisk");
	if(!temp)
		return EMEMERR;
	//Get the data from the device
	err = read_function(referrer, this, temp, block_offset, block_count);
	if(err) {
		malloc_free(temp);
		return err;
	}
	//Copy data from the offset where the required data starts
	memcpy(buf, temp + offset_in_block, count);
	//Free temporary buffer
	malloc_free(temp);
	return 0;
}


static int write_bytewise(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count,
		int (*read_function)(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count),
		int (*write_function)(struct fs *referrer, struct fs *this, void *buf, u32 offset, u32 count))
{
	int err;
	u32 block_offset;
	u32 block_count;
	u32 last_block_number;
	u32 offset_in_first_block, count_in_first_block;
	u32 count_in_last_block;
	//Calculate the offset in form of blocks
	block_offset = offset / SECT_SZ;
	//Calculate the number of blocks to be read
	block_count = count / SECT_SZ + 1;
	//Calculate the offset starting which we should copy the data to the destination block
	offset_in_first_block = offset - block_offset * SECT_SZ;
	//Calculate the number of bytes to replace in the first block
	if((offset_in_first_block + count) > SECT_SZ)
		count_in_first_block = SECT_SZ - offset_in_first_block;
	else
		count_in_first_block = count;
	//count_in_first_block = (SECT_SZ < count ? (SECT_SZ - offset_in_first_block) : count);
	//Calculate the number of bytes to replace in the last block
	if(block_count > 1 || ((offset / SECT_SZ) != ((offset + count) / SECT_SZ)))
		count_in_last_block = (offset + count) - (offset + count) / SECT_SZ * SECT_SZ;
	else
		count_in_last_block = 0;
	//Calculate the last block from (offset + count)
	last_block_number = (offset + count) / SECT_SZ;
	/*printk("write: %u, %u\n", offset, count);
	printk("offset_in_first_block = %u\n", offset_in_first_block);
	printk("count_in_first_block = %u\n", count_in_first_block);
	printk("count_in_last_block = %u\n", count_in_last_block);*/
	//Read the first block and the last block since they will remain partially changed
	void *first_block, *last_block;
	first_block = malloc_get(SECT_SZ, "bdisk");
	if(!first_block)
		return EMEMERR;
	last_block = malloc_get(SECT_SZ, "bdisk");
	if(!last_block)
		return EMEMERR;
	//Modify the first block
	err = read_function(referrer, this, first_block, block_offset, 1);
	if(err) {
		malloc_free(first_block);
		malloc_free(last_block);
		return err;
	}
	memcpy(first_block + offset_in_first_block, buf, count_in_first_block);
	err = write_function(referrer, this, first_block, block_offset, 1);
	if(err) {
		malloc_free(first_block);
		malloc_free(last_block);
		return err;
	}
	//Modify the last block
	err = read_function(referrer, this, last_block, last_block_number, 1);
	if(err) {
		malloc_free(first_block);
		malloc_free(last_block);
		return err;
	}
	memcpy(last_block, buf + (count - count_in_last_block), count_in_last_block);
	err = write_function(referrer, this, last_block, last_block_number, 1);
	if(err) {
		malloc_free(first_block);
		malloc_free(last_block);
		return err;
	}
	//Free some memory
	malloc_free(first_block);
	malloc_free(last_block);
	//Now write the central blocks
	err = write_function(referrer, this, buf + (SECT_SZ - offset_in_first_block), block_offset + 1, block_count - 1);
	return err;
}
